#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <pthread.h>

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

#include "port_comm.h"

using namespace std;
using namespace cv;

struct ThreadArgument
{
    int *status;
    VideoCapture *capture;
    int socket;
    sockaddr_in remote_info;
    bool *color;
    int *quality;
    //const char *window;
};

//void signal_handler(int sig);
void *send(void *arg);
Mat toGrey(Mat mat);
void printUsage();
//char *getWindowName(int c);

int main(int argc, char *argv[])
{
    if(argc != 3 && argc != 4)
    {
        printUsage();
        return 1;
    }
    
    /*// Register signal handler
    if(signal(SIGINT, signal_handler) == SIG_ERR){
        fprintf(stderr, "Unable to bind handler.\n");
        return 1;
    }*/

    char *host = argv[1];
    int port_number = atol(argv[2]);
    
    PortComm commClient(host, port_number, PortComm::CLIENT);
    
    int number = 11;
    if(argc == 4)
    {
        number = atoi(argv[3]) + 1;
    }

    VideoCapture *capture = new VideoCapture[number];
    bool *isOpen = new bool[number];
    //Mat frame;
    for(int c = 0; c < number; c++)
    {
        cout << c << "\n";
        capture[c] = VideoCapture(c);
        //capture[c].set(CV_CAP_PROP_MODE, MODE_640x480_MONO16);
        //capture[c].set(CV_CAP_PROP_FRAME_WIDTH, 320);
        //capture[c].set(CV_CAP_PROP_FRAME_HEIGHT, 240);
        isOpen[c] = capture[c].isOpened();
        /*if(isOpen[c])
        {
            while(true)
            {
                cout << "read " << c << "\n"; 
                capture[c].read(frame);
                //imshow
                cout << "end read\n";
                //capture[c].release();
                //capture[c].open(c);
            }
        }
        else
        {
            cout << "not open\n";
        }*/
    }
    //cout << "done\n";
    //while(true);
    
    commClient.write_port((unsigned char *) isOpen, sizeof(bool) * number);
    
    pthread_t *thread = new pthread_t[number];
    ThreadArgument *arg = new ThreadArgument[number];
    for(int c = 0; c < number; c++)
    {
        if(!isOpen[c])
        {
            continue;
        }

        arg[c].status = new int(-1);
        arg[c].capture = &capture[c];

        // Step 1 Look up server to get numeric IP address
        hostent * record = gethostbyname(host);
        if(record==NULL)
        {
            herror("gethostbyname failed");
            exit(1);
        }
        in_addr * addressptr = (in_addr *) record->h_addr;

        // Step 2 Create a socket
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock < 0)
        {
            perror("socket creation");
            exit(1);
        }
        arg[c].socket = sock;

        // Step 3 Create a sockaddr_in to describe the local port
        sockaddr_in local_info;
        local_info.sin_family = AF_INET;
        local_info.sin_addr.s_addr = htonl(INADDR_ANY);
        local_info.sin_port = htons(4000+c/*port_number + c + 1*/);

        // Step 4 Bind the socket to the port
        int r = bind(sock, (sockaddr *) &local_info, sizeof(local_info));
        if(r < 0)
        {
            perror("bind");
            exit(1);
        }

        // Step 5 Create a sockaddr_in to describe the remote application
        sockaddr_in remote_info;
        remote_info.sin_family = AF_INET;
        remote_info.sin_addr = *addressptr;
        remote_info.sin_port = htons(port_number + c + 1);
        arg[c].remote_info = remote_info;

        arg[c].color = new bool(true);
        arg[c].quality = new int(30);
        //arg[c].window = getWindowName(c);
        //namedWindow(arg[c].window, CV_WINDOW_AUTOSIZE);

        pthread_create(thread + c, NULL, send, (void *)(arg + c));
    }

    while(true)
    {
        int *message;

        int size;

        commClient.read_port((unsigned char **) &message, &size);

        for(int c = 0; c < number; c++)
        {
            if(isOpen[c])
            {
                if(message[c] == -1)
                {
                    *(arg[c].status) = -1;
                }
                else
                {
                    *(arg[c].status) = 1;
                    if(message[c] > 100)
                    {
                        *(arg[c].color) = true;
                        message[c] -= 101;
                    }
                    else
                    {
                        *(arg[c].color) = false;
                    }

                    *(arg[c].quality) = message[c];
                }
            }
        }

        delete []message;
    }

    return 0;
}

void *send(void *arg)
{
    ThreadArgument *targ = (ThreadArgument *) arg;
    vector<int> params;
    params.push_back(CV_IMWRITE_JPEG_QUALITY);
    params.push_back(*(targ->quality));
        
    Mat frame;
    char message[1000000];
    while(true)
    {
        while(*(targ->status) == -1)
        {
            usleep(10000);
        }
        if(!targ->capture->read(frame))
        {
            cout << "SKIP\n";
            continue;
        }

        //cout << targ->color << "\n";
        if(!*(targ->color))
        {
            frame = toGrey(frame);
        }
        //imshow(targ->window, frame);
        params[1] = *(targ->quality);
        //cout << params[1] << "\n";
        //Move to buf to encode to jpg
        vector<uchar> buf;
        if(!imencode("*.jpg", frame, buf, params))
        {
            fprintf(stderr, "Unable to encode image.\n");
            break;
        }

        //Change to char array to send
        //char *message = new char[buf.size()];
        for(int i = 0; i < (int)buf.size(); i++)
        {
            message[i] = (char)buf[i];
        }
        int r = sendto(targ->socket, message, buf.size(), MSG_EOR, (sockaddr *) &(targ->remote_info), sizeof(targ->remote_info));
        /*for(char c = '0'; c < targ->window[7]; c++)
        {
            cout << "\t";
        }*/
        //cout << targ->window << "\t" << r << "\n";
        if(r < 0)
        {
            //perror("sendto");
            cout << "sendto error\n";
        }
        //usleep(1000);
        //delete []message;
    }

    return NULL;
}

Mat toGrey(Mat mat)
{
    Mat ans(mat.rows, mat.cols, CV_8UC1);

    vector<Mat> plane;
    split(mat, plane);

    for(int r = 0; r < mat.rows; r++)
    {
        for(int c = 0; c < mat.cols; c++)
        {
            int sum = 0;
            for(int n = 0; n < (int)plane.size(); n++)
            {
                sum += plane[n].at<uchar>(r, c);
            }
            ans.at<uchar>(r, c) = (sum / (int)plane.size());
        }
    }

    return ans;
}

/*void signal_handler(int sig)
{
    printf("Interrupt Caught.\n");
    inter = true;
}*/

void printUsage()
{
    printf("Usage: camera <host> <port> [<number>]\n");
    return;
}

/*char *getWindowName(int c)
{
    vector<int> digit;
    if(c == 0)
    {
        digit.push_back(0);
    }
    while(c > 0)
    {
        digit.push_back(c % 10);
        c /= 10;
    }

    char *name = new char[8 + (int)digit.size()];
    name[0] = 'H';
    name[1] = 'e';
    name[2] = 'l';
    name[3] = 'l';
    name[4] = 'o';
    name[5] = '.';
    name[6] = '.';
    for(int c = 0; c < (int)digit.size(); c++)
    {
        name[6 + (int)digit.size() - c] = (char)(digit[c] + '0');
    }
    name[7 + (int)digit.size()] = (char) 0;

    cout << name << "\n";
    return name;
}*/

